using System;
using System.Data;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;


namespace Pam
{
    public class Pam
    {
        public static double MedoidMin = 0.0;//variable that contain a Min sum of Build step of the Pam aslgorithm
        public static ArrayList Indxarr = new ArrayList();
        public Pam()
        {
        }
        /// <summary>
        /// Calculates the Euclidean Distance Measure between two data points
        /// </summary>
        /// <param name="X">An array with the values of an object or datapoint</param>
        /// <param name="Y">An array with the values of an object or datapoint</param>
        /// <returns>Returns the Euclidean Distance Measure Between Points X and Points Y</returns>
        public static double EuclideanDistance(double[] X, double[] Y)
        {
            int count = 0;

            double distance = 0.0;

            double sum = 0.0;

            if (X.GetUpperBound(0) != Y.GetUpperBound(0))
            {
                throw new System.ArgumentException("the number of elements in X must match the number of elements in Y");
            }
            else
            {
                count = X.Length;
            }

            for (int i = 0; i < count; i++)
            {
                sum = sum + Math.Pow(Math.Abs(X[i] - Y[i]), 2);
            }

            distance = Math.Sqrt(sum);

            return distance;
        }

        /// <summary>
        /// Converts a System.Data.DataTable to a 2-dimensional array
        /// </summary>
        /// <param name="data">A System.Data.DataTable containing data to cluster</param>
        /// <returns>A 2-dimensional array containing data to cluster</returns>
        public static double[,] ConvertDataTableToArray(DataTable table)
        {
            int rowCount = table.Rows.Count;

            int fieldCount = table.Columns.Count;
            double[,] dataPoints;

            double fieldValue = 0.0;

            DataRow row;

            dataPoints = new double[rowCount, fieldCount];

            for (int rowPosition = 0; rowPosition < rowCount; rowPosition++)
            {
                row = table.Rows[rowPosition];

                for (int fieldPosition = 0; fieldPosition < fieldCount; fieldPosition++)
                {
                    try
                    {
                        fieldValue = double.Parse(row[fieldPosition].ToString());
                    }
                    catch (System.Exception ex)
                    {
                        System.Diagnostics.Debug.WriteLine(ex.ToString());

                        throw new InvalidCastException("Invalid row at " + rowPosition.ToString() + " and field " + fieldPosition.ToString(), ex);
                    }

                    dataPoints[rowPosition, fieldPosition] = fieldValue;
                }
            }
            /**********Print the Data Array***************************/
            System.Console.Out.WriteLine("The data array!!!\r\n");
            for (int i = 0; i < rowCount; i++)
            {
                for (int j = 0; j < fieldCount; j++)
                {
                    System.Console.Out.Write(dataPoints[i, j]);
                    System.Console.Out.Write("\t");

                }

                System.Console.Out.WriteLine("");
            }
            System.Console.Out.WriteLine("********************\r\n");
            /*************************************************************/
            return dataPoints;
        }
        /******** The first Step of Build Pam*******************************/
        public static void SumDistance(double[,] Dismat)
        {
            double sum = 0.0, minMedoid = 0.0;// minMedoid: variable that contain the min sum of Medoid
            int flag = 1, minIndx = 0;
            int szrow =Dismat.GetUpperBound(0)+1; 
            for (int i = 0; i < szrow  ; i++)
            {
                sum = 0.0;
                for (int j = 0; j < szrow; j++)
                {
                    sum += Dismat[i, j];
                }
                if (flag == 1)
                {
                    minMedoid = sum;
                    minIndx = i;
                    flag = 0;
                }
                Dismat[i, szrow] = sum;
                if (sum <= minMedoid)
                {
                    minMedoid = sum;
                    minIndx = i;
                }
            }
            Dismat[minIndx, szrow + 1] = 1;//if Medoid or Not   
            Indxarr.Add(minIndx);
            ///**************Print the Distance Matrix*******************/
            //for (int i = 0; i < szrow ; i++)
            //{
            //    for (int j = 0; j < szrow +2; j++)
            //        System.Console.Out.Write(Dismat[i, j] + "    ");
            //    System.Console.Out.WriteLine();
            //}
        }
        public static void BuildSwap(double[,] Dismat,  int Indx, int flagIndx,int clusternumber)
        {
            double Sum = 0.0, min = 0.0, MinSum = MedoidMin;
            int TempIndx = 0;
            int szrow = Dismat.GetUpperBound(0) + 1;  
            for (int i = 0; i < szrow; i++)
            {

                if (Dismat[i, szrow + 1] != 1)
                {
                    Sum = 0.0;
                    for (int j = 0; j < szrow; j++)
                    {
                        min = Dismat[i, j];
                        // System.Console.Out.WriteLine( min);
                        for (int m = 0; m < szrow; m++)
                        {
                            if (Dismat[m, szrow + 1] == 1 && flagIndx == 1)
                            {                                
                                if (Dismat[m, j] <= min)
                                {
                                    min = Dismat[m, j];

                                }                               
                            }
                            else//SwapStep
                            {

                                if (Dismat[m, szrow + 1] == 1 && m != Indx)
                                {                                  
                                    if (Dismat[m, j] <= min)
                                    {                                 
                                        min = Dismat[m, j];
                                    }
                                }
                            }
                        }
                        Sum += min;
                    }
                    Dismat[i, szrow] = Sum;
                    if (flagIndx == 2 && Sum < MedoidMin)
                    {
                        MinSum = Sum;
                        TempIndx = i;

                    }                    
                }
            }
            if (flagIndx == 2)
            {
                if (MinSum < MedoidMin)
                {
                    MedoidMin = MinSum;
                    Dismat[TempIndx, szrow + 1] = 1;
                    Dismat[Indx, szrow + 1] = 0;
                    Indxarr[clusternumber]=TempIndx;  

                }
            }
        }

        /*********The Build Step Of Pam**************************/
        public static void BuildStep(double[,] Dismat,  int K)
        {
            double minMedoid = 0.0;
            int minIndx = 0, flag = 1;
            int szrow = Dismat.GetUpperBound(0) + 1;   
            for (int k = 0; k < K - 1; k++)
            {
                BuildSwap(Dismat, -1, 1,-1);
                flag = 1;
                /**************Find the next Medoid******************/
                for (int n = 0; n < szrow; n++)
                {
                    if (flag == 1)
                    {
                        minMedoid = Dismat[n, szrow];
                        minIndx = n;
                        flag = 0;
                    }
                    else
                    {
                        if (Dismat[n, szrow] <= minMedoid)
                        {
                            minMedoid = Dismat[n, szrow];
                            minIndx = n;
                        }
                    }
                }
                Dismat[minIndx, szrow + 1] = 1;
                Indxarr.Add(minIndx);
            }
            MedoidMin = Dismat[minIndx, szrow];
            ///////**************Print the Distance Matrix*******************/
            for (int i = 0; i < szrow; i++)
            {
                for (int j = 0; j < szrow + 2; j++)
                    System.Console.Out.Write(Dismat[i, j] + "    ");
                System.Console.Out.WriteLine("\r\n\n\n");
            }
        }
        /*********The Swap Step Of Pam**************************/
        public static void SwapStep(double[,] Dismat,  int K)
        {

            int szrow = Dismat.GetUpperBound(0) + 1; 
            for (int i = 0; i < K; i++)
            {
                BuildSwap(Dismat, int.Parse(Indxarr[i].ToString()), 2,i);
            }

            ///////**************Print the Distance Matrix*******************/
            for (int i = 0; i < szrow; i++)
            {
                for (int j = 0; j < szrow + 2; j++)
                    System.Console.Out.Write(Dismat[i, j] + "    ");
                System.Console.Out.WriteLine("\r\n\n\n");
            }
        }

        /// <summary>
        /// Seperates a dataset into clusters or groups with similar characteristics
        /// </summary>
        /// <param name="clusterCount">The number of clusters or groups to form</param>
        /// <param name="data">An array containing data that will be clustered</param>
        /// <returns>A collection of clusters of data</returns>
        public static ClusterCollection ClusterDataSet(int clusterCount, double[,] Dismat,double[,] data)
        {
            int rowCount = data.GetUpperBound(0) + 1;                      
            
            int fieldCount = data.GetUpperBound(1) + 1;
            //int fieldCount = data.GetUpperBound(1) ;          
            
            double[] dataPoint;
            double min;
            int minIndx;
           
            System.Collections.ArrayList clusterNumbers = new System.Collections.ArrayList(clusterCount);
            
           //create collection of clusters
            ClusterCollection clusters = new ClusterCollection();
            if (clusterCount <= 0)
            {
                throw new SystemException("Cluster Count Cannot Be Negative OR zerro!!!");
            }            

            for (int count = 0; count < clusterCount; count++)
            {
                Cluster cluster = new Cluster();
                clusters.Add(cluster);
            }

            /****************************Put the Medoid's in a Clusters List********************/
            for (int position= 0; position  < clusterCount; position++)
            {
               
                dataPoint = new double[fieldCount];  
                for (int field = 0; field < fieldCount; field++)
                {             
                    dataPoint.SetValue((data[int.Parse(Indxarr[position].ToString()), field]), field);                 

                }               
                clusters[position].Add(dataPoint);   
            }
       
            for (int i = 0; i < rowCount; i++)
            {
                if(Dismat[i,rowCount+1]!=1)
                {
                    min=Dismat[i,int.Parse (Indxarr[0].ToString()) ];
                    minIndx=0;
                    for(int j=1;j<clusterCount;j++)
                    {
                        if (Dismat[i, int.Parse(Indxarr[j].ToString())] < min)
                        {
                            min=Dismat[i,int.Parse (Indxarr[j].ToString ())];
                            minIndx =j;
                        }
                    }

                    dataPoint = new double[fieldCount];  
                    for (int field = 0; field < fieldCount; field++)
                    {
                        dataPoint.SetValue((data[i,field]), field);       
                    }                
                    clusters[minIndx].Add(dataPoint);  
                }                
            }                                          
             return clusters;            
        }    
    }

    /// <summary>
    /// A class containing a group of data with similar characteristics (cluster)
    /// </summary>
    [Serializable]
    public class Cluster : System.Collections.CollectionBase
    {
        private double[] _clusterSum;
        /// <summary>
        /// The sum of all the data in the cluster
        /// </summary>
        public double[] ClusterSum
        {
            get
            {
                return this._clusterSum;
            }
        }

        private double[] _clusterMean;
        /// <summary>
        /// The mean of all the data in the cluster
        /// </summary>
        public double[] ClusterMean
        {
            get
            {
               // for (int count = 0; count < this[0].Length; count++)
                for (int count = 0; count < this[0].Length-1; count++)
                {
                    this._clusterMean[count] = (this._clusterSum[count] / this.List.Count);
                }               
                return this._clusterMean;
            }
        }

        /// <summary>
        /// Adds a single dimension array data to the cluster
        /// </summary>
        /// <param name="data">A 1-dimensional array containing data that will be added to the cluster</param>
        //  public ArrayList list = new ArrayList();

        public virtual void Add(double[] data)
        {           
            this.List.Add(data);
            if (this.List.Count == 1)
            {
                //this._clusterSum = new double[data.Length];

                //this._clusterMean = new double[data.Length];
                
                this._clusterSum = new double[data.Length-1];

                this._clusterMean = new double[data.Length-1];
            }

            //for (int count = 0; count < data.Length-1; count++)
             for (int count = 0; count < data.Length-1; count++)            
            {
                this._clusterSum[count] = this._clusterSum[count] + data[count];
            }           
        }

        /// <summary>
        /// Returns the one dimensional array data located at the index
        /// </summary>
        public virtual double[] this[int Index]
        {
            get
            {
                //return the Neuron at IList[Index] 
                return (double[])this.List[Index];
            }
        }
    }

    /// <summary>
    /// A collection of Cluster objects or Clusters
    /// </summary>
    [Serializable]
    public class ClusterCollection : System.Collections.CollectionBase
    {
        /// <summary>
        /// Adds a Cluster to the collection of Clusters
        /// </summary>
        /// <param name="cluster">A Cluster to be added to the collection of clusters</param>
        public virtual void Add(Cluster cluster)
        {

            this.List.Add(cluster);
            // System.Console.Out.WriteLine(List.Count );  
        }

        /// <summary>
        /// Returns the Cluster at this index
        /// </summary>
        public virtual Cluster this[int Index]
        {
            get
            {
                //return the Neuron at IList[Index] 			                              
                return (Cluster)this.List[Index];
            }
        }
    }

         



         

 }
